import math
from math import *

import pyglet
from pyglet.window import key, mouse
from pyglet.gl import *
from pyglet import gl

import cocos
from cocos.actions import *
from cocos.layer import *
from cocos.scene import *
from cocos.director import director
from cocos.scenes.transitions import *
from cocos.euclid import Point2
from cocos.draw import *
from cocos.text import *

import Box2D as box2d

import ui
import listener
import vec2util
import box
import map
import weapon
from box import *
from map import *
from prop import *
from globalvars import *

# Simple func to draw line with opengl

def drawLine(a,b):
    "Draw a line"
    glColor3f(0.0,0.0,0.0)
    glBegin(GL_LINES)
    glVertex3f(a[0],a[1], 4)
    glVertex3f(b[0],b[1],4)
    glEnd()

###--------------------------###       
#        
#    GameWorld
#
###---------------------------### 

class GameWorld(ColorLayer, box2d.b2World):
    """
    GameWorld handles 
    - Entities
    - Rendering with cocos.layer.ColorLayer
    - Physics world with box2d.b2World,
    - Physics contacts with GameContactListener and GameContactPoint
    """
    
    is_event_handler = True
    
    DT = 1.0/60.0
    VEL_ITERS = 10
    POS_ITERS = 8
    
    ZOOM_STEP = 0.04
    DEFAULT_ZOOM = 0.3
    viewZoom = None
    isPaused = None
    
    def __init__(self):
        "Cocos2d and gameplay related"
        ColorLayer.__init__(self,255,255,255,0)#,1000,1000)
        self.scale = self.DEFAULT_ZOOM
        self.sprites = self.children # merely a pointer
        self.isPaused = False
        
        "entity"
        self.entities = {}
        
        "box2d world init"
        worldAABB=box2d.b2AABB()
        worldAABB.lowerBound = (-500, -500.0)
        worldAABB.upperBound = ( 500.0, 500.0)
        box2d.b2World.__init__(self, worldAABB, (0,0), True)
        self.gravity = (0,0)
        
        "The listeners"
        self.points = []
        self.contactListener = listener.GameContactListener()
        self.contactListener.test = self
        self.SetContactListener(self.contactListener)
        #self.debugDraw = listener.GameDebugDraw()
        #self.debugDraw.surface = director.window.screen
        #self.SetDebugDraw(self.debugDraw)
    
        "The map"
        self.map = Map1(self)
        self.playerBox = self.map.playerBox
        self.add(self.map,z=-1)

        
        
    def on_update(self,dt):
        "Update sprites, contact listener, world"
        self.parent.gameHUD.update(dt)
        
        for entity in self.entities.values():
            entity.update(dt)
        
        self.followPlayerBox()
        self.points = []
        self.Step(self.DT, self.VEL_ITERS, self.POS_ITERS)
        self.Validate()
        #self.updateProjection()
        
    def on_key_press(self, symbol, modifiers):
        "Open pause menu"
        if symbol == key.ESCAPE:
            self.parent.on_pause()
            return True
    
    def on_mouse_motion(self,x,y,dx,dy):
        self.parent.gameHUD.on_mouse_motion(x, y, dx, dy)
    
    def on_mouse_press(self, x, y, button, modifiers):
        self.parent.gameHUD.on_mouse_press(x, y, button, modifiers)
    
    def on_mouse_release(self, x, y, button, modifiers):
        self.parent.gameHUD.on_mouse_release(x, y, button, modifiers)
        
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        self.parent.gameHUD.on_mouse_drag(x, y, dx, dy, buttons, modifiers)
        
    def on_mouse_scroll(self, x, y, dx, dy):
        self.scale += dy*self.ZOOM_STEP
       
    def on_activate(self):
        "continue update if window is active"
        pyglet.clock.unschedule(self.on_update)
        pyglet.clock.schedule_interval(self.on_update, self.DT)
        
    def on_deactivate(self):
        "pause update if window is inactive"
        pyglet.clock.unschedule(self.on_update)
    
    def followPlayerBox(self):
        "Update view"
        x, y = director.get_window_size()
        self.x = -self.playerBox.x + x/2
        self.y = -self.playerBox.y + y/2
        self.transform_anchor_x = self.playerBox.x
        self.transform_anchor_y = self.playerBox.y 
    
    def addEntity(self,newEntity,*args,**kwargs):
        "Add an entity"
        self.entities[newEntity.id] = newEntity
        self.add(newEntity,*args,**kwargs)
        
    def removeEntity(self,idOrEntity):
        "Remove entity given itself or its ID"
        if type(idOrEntity) == int:
            ent = self.getEntityByID(idOrEntity)
            self.remove(ent)
            del self.entities[idOrEntity]
            #del ent
        else:
            id = self.getIDByEntity(idOrEntity)
            self.remove(idOrEntity)
            del self.entities[id]
            #del idOrEntity
        
    def getEntityByID(self,id):
        return self.entities[idOrEntity]
        
    def getIDByEntity(self,entity):
        return ([k for k, v in self.entities.iteritems() if v == entity][0])
###--------------------------###       
#        
#    GameHUD
#
###---------------------------### 

class GameHUD(ColorLayer):
    "Status report- headsup display"
    def __init__(self):
        "Init"
        ColorLayer.__init__(self,255,255,255,0)
        self.winSize = director.window.get_size()
        #self.scale = 1
        #self.offset = box2d.b2Vec2(-260,-160)
        self.playerBox = None
        
        "analog sticks"
        self.analogSticks = []
        self.velocityAnalogStick = self.VelocityAnalogStick(position=(90,70))
        self.weaponAnalogStick = self.WeaponAnalogStick(position=(380,75))
        self.analogSticks.append(self.velocityAnalogStick)
        self.analogSticks.append(self.weaponAnalogStick)
        
        "HP meter"
        self.meters = []
        self.HPMeter = self.HPMeter()
        self.HPMeter.position = (40,290)
        self.ammoMeter = self.AmmoMeter()
        self.ammoMeter.position = (40,250)
        self.meters.append(self.HPMeter)
        self.meters.append(self.ammoMeter)
        
    def setPlayerBox(self, playerBox):
        "set the player box so we can use the input systems"
        self.playerBox = playerBox
        
        for a in self.analogSticks:
            a.setPlayerBox(playerBox)
            self.add(a)
            self.add(a.nub, z=1)
  
        self.HPMeter.setObjectAndAttr(playerBox, "hp")
        self.ammoMeter.setObjectAndAttr(playerBox, "activeWeapon") # ammo, see below
        
        for m in self.meters:
            self.add(m)

    def update(self,dt):
        for a in self.analogSticks:
            a.update(dt)
        for m in self.meters:
            m.update(dt)

    def on_mouse_motion(self,x,y,dx,dy):
        for a in self.analogSticks:
            a.on_mouse_motion(x, y, dx, dy)
    
    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        for a in self.analogSticks:
            a.on_mouse_drag(x, y, dx, dy, buttons, modifiers)
            
    def on_mouse_press(self, x, y, button, modifiers):
        for a in self.analogSticks:
            a.on_mouse_press(x, y, button, modifiers)
        
    def on_mouse_release(self, x, y, button, modifiers):
        for a in self.analogSticks:
            a.on_mouse_release(x, y, button, modifiers)
        
    class BaseAnalogStick(Sprite):
        "On-screen analog stick"
        def __init__(self,*args, **kwargs):
            super(GameHUD.BaseAnalogStick, self).__init__(Images.analog_stick_base, *args, **kwargs)
            self.nub = Sprite(Images.analog_stick, position=self.position, opacity=0)
            self.scale = 1
            self.analogVec = box2d.b2Vec2(0,0)
            self.fingerDown = False
            self.playerBox = None
            
        def setPlayerBox(self,playerBox):
            self.playerBox = playerBox
        
        def update(self,dt):
            "Update stub"
            pass
           
        def on_mouse_motion(self,x,y,dx,dy):
            "if mouse moved ,update unpressed analog nub pos"
            self.updateAnalogNubPos(x,y)

        def on_mouse_press(self, x, y, button, modifiers):
            "Start computing nub pos"
            if (vec2util.get_distance_to(self.position, (x,y)) <= ((self.width/2))*self.scale ):
                self.fingerDown = True
                self.updateAnalogNubPos(x,y)
                self.updateAnalogVec(x,y)
            else: 
                self.fingerDown = False

        def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
            "if mouse dragged Update analog nub and vector"
            if self.fingerDown:
                self.updateAnalogNubPos(x,y)
                self.updateAnalogVec(x,y)
        
        def on_mouse_release(self, x, y, button, modifiers):
            "Release the nub"
            self.nub.opacity = 0
            
        def updateAnalogNubPos(self, x,y):
            "Update the nub pos"
            self.nub.position = x,y
            if (vec2util.get_distance_to(self.position, (x,y)) > ((self.width/2)*self.scale) ):
                pos = self.position
                d = vec2util.normalize((pos[0] - x, pos[1] - y))
                nx = pos[0] - d[0]*(self.width/2)*self.scale
                ny = pos[1] - d[1]*(self.height/2)*self.scale
                self.nub.position = (nx,ny)
        
        def updateAnalogVec(self,x,y):
            "Update the analog Vec"
            if (vec2util.get_distance_to(self.position, (x,y)) > ((self.width/2)*self.scale)):
                pos = self.position
                d = vec2util.normalize((pos[0] - x, pos[1] - y))
                dx = d[0]*self.width/2
                dy = d[1]*self.width/2
                self.analogVec = box2d.b2Vec2(dx,dy)
            else:
                self.analogVec =  box2d.b2Vec2(self.position[0] - x, self.position[1] - y)
            self.nub.opacity = 255
            
             
    class VelocityAnalogStick(BaseAnalogStick):
        "Moves playerbox"
        def __init__(self,*args, **kwargs):
            super(GameHUD.VelocityAnalogStick, self).__init__(*args, **kwargs)
            self.scale = 1
            
        def update(self,dt):
            "No need to do anything here"
            #if sefl.playerBox:
            #    self.playerBox.velocityVec = self.analogVec
             
        def on_mouse_release(self, x, y, button, modifiers):
            "Relase nub and reset analogVec"
            self.nub.opacity = 0
            self.analogVec = box2d.b2Vec2(0,0)
           
           
    class WeaponAnalogStick(BaseAnalogStick):
        "Moves weapon"
        def __init__(self,*args, **kwargs):
            super(GameHUD.WeaponAnalogStick, self).__init__(*args, **kwargs)
            self.analogVec = box2d.b2Vec2(-1,0)
            self.prevVec = box2d.b2Vec2(-1,0)
            self.isTriggered = False
            self.scale = 1.4
            
        def update(self,dt):
            if self.playerBox:
                if self.playerBox.activeWeapon:
                    self.playerBox.weaponVec = self.analogVec
                    self.playerBox.weaponAngle = self.getAngleFromVec()
                    if self.isTriggered:
                        self.playerBox.onTriggerWeapon(dt)
                    else:
                        self.playerBox.offTriggerWeapon(dt)
    
        
        def on_mouse_release(self, x, y, button, modifiers):
            self.nub.opacity = 0
            self.prevVec = self.analogVec
            self.isTriggered = False
        
        def updateAnalogVec(self,x,y):
            if self.playerBox:
                if self.playerBox.activeWeapon:
                    self.playerBox.activeWeapon.updateAnalogVecFunc(self,x,y)
                    self.nub.opacity = 255

        def getAngleFromVec(self):
            xs = self.analogVec.x
            ys = self.analogVec.y
            theta = atan2(ys,xs)
            if theta < 0:
                theta += 2*pi
            return theta # in radians
               
    class BaseMeter(Canvas):
        def __init__(self, ratio=1, 
                     text="Unnamed Bar", textColor = (100,100,100,255), textSize=12, 
                     fgColor=(50,200,130,150), fgWidth=30, 
                     bgColor=(100,255,160,160), bgWidth = 20 ):
            "The HP Meter"
            Canvas.__init__(self)
            
            "Start and End"
            self.start = (0,0)
            self.end = (0,0)
            self.ratio = ratio
            self.object = None
            self.objectAttr = ""
            self.value = 0
            self.endValue = 0
            
            "Colors and width"
            self.fgColor = fgColor
            self.fgWidth = fgWidth
            self.bgColor = bgColor
            self.bgWidth = bgWidth            

            "Label"
            self.label = Label(text,anchor_x="center", anchor_y="center",
                               position =(20,0), font_size = textSize, color = textColor ) 
            self.add(self.label)
            
        def setObjectAndAttr(self,obj,attr):
            "To be implemented - set object ref and its attr"
    
    
        def updateValue(self):
            "To be implemented - update the value based on self.object and attr"
            pass
                
            
        def update(self,dt):
            "Updates"
            self.updateValue()
            if self.value != self.end[0]:
                self.end = (self.value,0)
                self._dirty = True
                self.draw()
            
        def render(self):
            "Render"
            self.set_color(self.bgColor)
            self.set_stroke_width(self.bgWidth)
            self.move_to(self.start)
            self.line_to((self.endValue, self.end[1]))
            
            self.set_color(self.fgColor)
            self.set_stroke_width(self.fgWidth)
            self.move_to(self.start)
            self.line_to(self.end)
            
           
                
    class HPMeter(BaseMeter):
        def __init__(self, ratio=1, text="HP", textColor = (50,50,50,255), textSize=15,
                    fgColor=(50,255,50,250), fgWidth=20, 
                     bgColor=(50,255,50,100), bgWidth = 30 ):
            GameHUD.BaseMeter.__init__(self, ratio, text, textColor, textSize, fgColor, fgWidth, bgColor, bgWidth )
            
        def setObjectAndAttr(self,obj,attr):
            "Ref-ed value"
            self.object = obj
            self.objectAttr = attr
            if self.object:
                exec("self.endValue = self.object."+self.objectAttr+ "*self.ratio")
    
    
        def updateValue(self):
            if self.object:
                exec("self.value = self.object."+self.objectAttr+ "*self.ratio")
    
    class AmmoMeter(BaseMeter):
        def __init__(self, ratio=4, text="Ammo", textColor = (200,200,200,255), textSize=10,
                     fgColor=(100,50,200,250), fgWidth=15, 
                     bgColor=(100,50,150,100), bgWidth = 20 ):
            GameHUD.BaseMeter.__init__(self, ratio, text, textColor, textSize, fgColor, fgWidth, bgColor, bgWidth )
            
        def setObjectAndAttr(self,obj,attr):
            "Ref-ed value"
            self.object = obj
            self.objectAttr = attr
    
        def updateValue(self):
            if self.object:
                exec("test= self.object."+self.objectAttr)
                if issubclass(type(test), weapon.Weapon):
                    exec("self.endValue = self.object."+self.objectAttr+".maxAmmo" + " * self.ratio")
                    exec("self.value = self.object."+self.objectAttr+".ammo" + " * self.ratio")
                else:
                    self.endValue = self.value = 0

        
class GameScene(Scene):
    "Game Scene"
    
    def __init__(self):
        Scene.__init__(self)
        self.menuOverlay = None
        
        self.gameWorld = GameWorld()
        self.gameHUD = GameHUD()
        self.gameHUD.setPlayerBox(self.gameWorld.playerBox)
        
        self.add(self.gameHUD, z=3)
        self.add(self.gameWorld, z=2)
        
        self.schedule_interval(self.gameWorld.on_update, self.gameWorld.DT)
        
    def on_pause(self):
        self.gameWorld.isPaused = True
        pyglet.clock.unschedule(self.gameWorld.on_update)
        self.menuOverlay = ui.MenuOverlay(self.gameWorld.isPaused)
        self.add(self.menuOverlay,z=4)
        self.menuOverlay.bg.image.opacity = 0
        self.menuOverlay.bg.image.do(FadeIn(0.2))
        
    def on_resume(self):
        self.gameWorld.isPaused = False
        pyglet.clock.schedule_interval(self.gameWorld.on_update, self.gameWorld.DT)
        self.remove(self.menuOverlay)
        self.menuOverlay = None
        
    def on_end_game(self):
        self.remove(self.menuOverlay)
        self.menuOverlay = None
        director.set_projection()
        director.replace(ZoomTransition(ui.MenuScene(), duration=0.3))
        
    


